#include "CommandList.h"
#include "Device.h"
#include <stdexcept>

namespace Bonnie {
    UINT64 CommandList::GlobeFenceValue = 0;

    CommandList::SharedPtr CommandList::create()
    {
        static int i = 0;

        if(i >= Device::NUM_BACK_BUFFERS)
            throw std::runtime_error("one commandList per frame");

        auto cmdList = std::make_shared<CommandList>();

        cmdList->mCmdListIdx = i;
        cmdList->mQueue = Device::get()->mCmdQueue;


        auto& device = Device::get()->mDevice;
        {
            if (device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdList->mCommandAllocator)) != S_OK)
                throw std::runtime_error("failed to create allocator");
        }

        {
            if (device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdList->mCommandAllocator.Get(), NULL, IID_PPV_ARGS(&cmdList->mGraphCmdList)) != S_OK ||
                cmdList->mGraphCmdList->Close() != S_OK)
                throw std::runtime_error("failed to create cmdlist");
        }

        auto& swapChain = Device::get()->mSwapChain;
        {
            SIZE_T rtvDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
            D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = Device::get()->mRtvDescHeap->GetCPUDescriptorHandleForHeapStart();

            rtvHandle.ptr += i * rtvDescriptorSize;
            cmdList->mRenderTargetDescriptor = rtvHandle;

            cmdList->onResize();
        }

        i++;
        return cmdList;
    }
    void CommandList::beginRecord()
    {
        Device::get()->waitGPUAchieve(mFenceValue);

        mCommandAllocator->Reset();
        mGraphCmdList->Reset(mCommandAllocator.Get(), NULL);

        D3D12_RESOURCE_BARRIER barrier = {};
        barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
        barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
        barrier.Transition.pResource = mRenderTargetResource.Get();
        barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
        barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
        barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
        mGraphCmdList->ResourceBarrier(1, &barrier);
    }
    void CommandList::onResize()
    {
        mRenderTargetResource.Reset();

        ID3D12Resource* pBackBuffer = NULL;
        Device::get()->mSwapChain->GetBuffer(mCmdListIdx, IID_PPV_ARGS(&pBackBuffer));
        Device::get()->mDevice->CreateRenderTargetView(pBackBuffer, NULL, mRenderTargetDescriptor);
        mRenderTargetResource = pBackBuffer;
    }
    void CommandList::clearDefaultRTV(const FLOAT* cleanColor)
    {
        clearRTV(mRenderTargetDescriptor, cleanColor);
    }
    void CommandList::clearRTV(D3D12_CPU_DESCRIPTOR_HANDLE rtv, const FLOAT* cleanColor)
    {
        mGraphCmdList->ClearRenderTargetView(rtv, cleanColor, 0, NULL);
    }
    void CommandList::setDefaultRTV()
    {
        setRTVs({ mRenderTargetDescriptor });
    }
    void CommandList::setRTVs(const std::vector<D3D12_CPU_DESCRIPTOR_HANDLE>& rtvs, const D3D12_CPU_DESCRIPTOR_HANDLE * dsv)
    {
        mGraphCmdList->OMSetRenderTargets(1, rtvs.data(), FALSE, NULL);
    }
    UINT64 CommandList::endRecord()
    {
        D3D12_RESOURCE_BARRIER barrier = {};
        barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
        barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
        barrier.Transition.pResource = mRenderTargetResource.Get();
        barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
        barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
        barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
        mGraphCmdList->ResourceBarrier(1, &barrier);
        mGraphCmdList->Close();
        mQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)mGraphCmdList.GetAddressOf());

        mFenceValue = ++GlobeFenceValue;
        return mFenceValue;
    }
}
